<i18n locale="en" lang="yaml">
lastSucceeded : 'Succeeded {t}'
lastFailed    : 'Failed {t}'
lastRan       : 'Ran {t}'
successCount  : 'Success {n}'
failureCount  : 'Failure {n}'
</i18n>

<i18n locale="zh-CN" lang="yaml">
Jump to...                                     : 跳转到...
Refresh                                        : 刷新列表
New Script                                     : 新建脚本
New Script Set                                 : 新建脚本集
Edited                                         : 已修改
Others are allowed to operate                  : 允许其他人操作
Locked by other user ({user})                  : 被其他用户（{user}）锁定
Locked by you                                  : 被您锁定
Quick View                                     : 快速查看
Members                                        : 成员
View                                           : 查看
Setup                                          : 配置
Example (In Same Script Set)                   : 示例（在相同脚本集中）
Example (In Other Script Set)                  : 示例（在其他脚本集中）
Copy {name} ID                                 : 复制{name} ID
Code edited but not published yet              : 代码已修改但尚未发布
'Import/Calling will run the published version': 引用 / API 调用实际将运行已发布代码
Export Script Set                              : 导出脚本集
'Assigned to queue #{queue}'                   : '指派到队列 #{queue}'
Script Set {id}                                : 脚本集 {id}
Script {id}                                    : 脚本 {id}

Please input ID                                   : 请输入 ID
Script Set ID too long                            : 脚本集 ID 过长
Only alphabets, numbers and underscore are allowed: 只能包含大小写英文、数字及下划线
Cannot not starts with a number                   : 不得以数字开头
Please input new Script Set ID                    : 请输入新脚本集 ID
Inputed Script Set ID already exists              : 输入的脚本集 ID 已经存在

Script Set pinned  : 脚本集已置顶
Script Set unpinned: 脚本集已取消
Script Set locked  : 脚本集已上锁
Script Set unlocked: 脚本集已解锁
Script Set cloned  : 脚本集已克隆
Script Set exported: 脚本集已导出
Script Set deleted : 脚本集已删除
Script locked      : 脚本已上锁
Script unlocked    : 脚本已解锁
Script deleted     : 脚本已删除

Installed form Script Market      : 从脚本市场安装的脚本集
The Script Market has been removed: 脚本市场已被删除

Config    : 配置
Auth      : 认证
Cron Expr : Cron 表达式
Expires   : 过期
Throttling: 限流
Created   : 创建
Run       : 执行

Sync API List : 同步 API 列表
Async API List: 异步 API 列表
Cron Job List : 定时任务列表

lastSucceeded : '{t}调用成功'
lastFailed    : '{t}调用失败'
lastRan       : '{t}调用'
successCount  : '成功 {n}'
failureCount  : '失败 {n}'

Are you sure you want to delete the Script Set?<br><strong class="text-bad">{label}</strong>: 是否确认删除此脚本集?<br><strong class="text-bad">{label}</strong>
Are you sure you want to delete the Script?<br><strong class="text-bad">{label}</strong>: 是否确认删除此脚本<br><strong class="text-bad">{label}</strong>
Are you sure you want to start the Cron Job manually?: 是否确认手动启动此定时任务？
Cron Job Started Manually: 定时任务已手工启动

Go to Script Publish History  : 前往脚本发布历史
Sync, Async APIs and Cron Jobs: 同步、异步 API 和定时任务
Go to Recent Task Record      : 前往最近任务记录
Go to Script Market           : 前往脚本市场

Some Script Sets have been hidden: 一些脚本集已被隐藏
"In most cases, the Script Sets that users doesn't need to care about are hidden by default.": "大多数情况下，用户不需要关心的脚本集默认会被隐藏。"
"You (can) change the default behavior in [Management / System Settings]."                     : "您可以在「管理 / 系统设置」中修改此默认行为。"
</i18n>

<!-- Generated by OpenCC START -->
<i18n locale="zh-HK" lang="yaml">
Are you sure you want to delete the Script Set?<br><strong class="text-bad">{label}</strong>: 是否確認刪除此腳本集?<br><strong class="text-bad">{label}</strong>
Are you sure you want to delete the Script?<br><strong class="text-bad">{label}</strong>: 是否確認刪除此腳本<br><strong class="text-bad">{label}</strong>
Are you sure you want to start the Cron Job manually?: 是否確認手動啓動此定時任務？
'Assigned to queue #{queue}': '指派到隊列 #{queue}'
Async API List: 異步 API 列表
Auth: 認證
Cannot not starts with a number: 不得以數字開頭
Code edited but not published yet: 代碼已修改但尚未發佈
Config: 配置
Copy {name} ID: 複製{name} ID
Created: 創建
Cron Expr: Cron 表達式
Cron Job List: 定時任務列表
Cron Job Started Manually: 定時任務已手工啓動
Edited: 已修改
Example (In Other Script Set): 示例（在其他腳本集中）
Example (In Same Script Set): 示例（在相同腳本集中）
Expires: 過期
Export Script Set: 導出腳本集
Go to Recent Task Record: 前往最近任務記錄
Go to Script Market: 前往腳本市場
Go to Script Publish History: 前往腳本發佈歷史
Import/Calling will run the published version: 引用 / API 調用實際將運行已發佈代碼
In most cases, the Script Sets that users doesn't need to care about are hidden by default.: 大多數情況下，用户不需要關心的腳本集默認會被隱藏。
Inputed Script Set ID already exists: 輸入的腳本集 ID 已經存在
Installed form Script Market: 從腳本市場安裝的腳本集
Jump to...: 跳轉到...
Locked by other user ({user}): 被其他用户（{user}）鎖定
Locked by you: 被您鎖定
Members: 成員
New Script: 新建腳本
New Script Set: 新建腳本集
Only alphabets, numbers and underscore are allowed: 只能包含大小寫英文、數字及下劃線
Others are allowed to operate: 允許其他人操作
Please input ID: 請輸入 ID
Please input new Script Set ID: 請輸入新腳本集 ID
Quick View: 快速查看
Refresh: 刷新列表
Run: 執行
Script Set ID too long: 腳本集 ID 過長
Script Set cloned: 腳本集已克隆
Script Set deleted: 腳本集已刪除
Script Set exported: 腳本集已導出
Script Set locked: 腳本集已上鎖
Script Set pinned: 腳本集已置頂
Script Set unlocked: 腳本集已解鎖
Script Set unpinned: 腳本集已取消
Script Set {id}: 腳本集 {id}
Script deleted: 腳本已刪除
Script locked: 腳本已上鎖
Script unlocked: 腳本已解鎖
Script {id}: 腳本 {id}
Setup: 配置
Some Script Sets have been hidden: 一些腳本集已被隱藏
Sync API List: 同步 API 列表
Sync, Async APIs and Cron Jobs: 同步、異步 API 和定時任務
The Script Market has been removed: 腳本市場已被刪除
Throttling: 限流
View: 查看
You (can) change the default behavior in [Management / System Settings].: 您可以在「管理 / 系統設置」中修改此默認行為。
failureCount: 失敗 {n}
lastFailed: '{t}調用失敗'
lastRan: '{t}調用'
lastSucceeded: '{t}調用成功'
successCount: 成功 {n}
</i18n>
<i18n locale="zh-TW" lang="yaml">
Are you sure you want to delete the Script Set?<br><strong class="text-bad">{label}</strong>: 是否確認刪除此指令碼集?<br><strong class="text-bad">{label}</strong>
Are you sure you want to delete the Script?<br><strong class="text-bad">{label}</strong>: 是否確認刪除此指令碼<br><strong class="text-bad">{label}</strong>
Are you sure you want to start the Cron Job manually?: 是否確認手動啟動此定時任務？
'Assigned to queue #{queue}': '指派到佇列 #{queue}'
Async API List: 非同步 API 列表
Auth: 認證
Cannot not starts with a number: 不得以數字開頭
Code edited but not published yet: 程式碼已修改但尚未釋出
Config: 配置
Copy {name} ID: 複製{name} ID
Created: 建立
Cron Expr: Cron 表示式
Cron Job List: 定時任務列表
Cron Job Started Manually: 定時任務已手工啟動
Edited: 已修改
Example (In Other Script Set): 示例（在其他指令碼集中）
Example (In Same Script Set): 示例（在相同指令碼集中）
Expires: 過期
Export Script Set: 匯出指令碼集
Go to Recent Task Record: 前往最近任務記錄
Go to Script Market: 前往指令碼市場
Go to Script Publish History: 前往指令碼釋出歷史
Import/Calling will run the published version: 引用 / API 呼叫實際將執行已釋出程式碼
In most cases, the Script Sets that users doesn't need to care about are hidden by default.: 大多數情況下，使用者不需要關心的指令碼集預設會被隱藏。
Inputed Script Set ID already exists: 輸入的指令碼集 ID 已經存在
Installed form Script Market: 從指令碼市場安裝的指令碼集
Jump to...: 跳轉到...
Locked by other user ({user}): 被其他使用者（{user}）鎖定
Locked by you: 被您鎖定
Members: 成員
New Script: 新建指令碼
New Script Set: 新建指令碼集
Only alphabets, numbers and underscore are allowed: 只能包含大小寫英文、數字及下劃線
Others are allowed to operate: 允許其他人操作
Please input ID: 請輸入 ID
Please input new Script Set ID: 請輸入新指令碼集 ID
Quick View: 快速檢視
Refresh: 重新整理列表
Run: 執行
Script Set ID too long: 指令碼集 ID 過長
Script Set cloned: 指令碼集已克隆
Script Set deleted: 指令碼集已刪除
Script Set exported: 指令碼集已匯出
Script Set locked: 指令碼集已上鎖
Script Set pinned: 指令碼集已置頂
Script Set unlocked: 指令碼集已解鎖
Script Set unpinned: 指令碼集已取消
Script Set {id}: 指令碼集 {id}
Script deleted: 指令碼已刪除
Script locked: 指令碼已上鎖
Script unlocked: 指令碼已解鎖
Script {id}: 指令碼 {id}
Setup: 配置
Some Script Sets have been hidden: 一些指令碼集已被隱藏
Sync API List: 同步 API 列表
Sync, Async APIs and Cron Jobs: 同步、非同步 API 和定時任務
The Script Market has been removed: 指令碼市場已被刪除
Throttling: 限流
View: 檢視
You (can) change the default behavior in [Management / System Settings].: 您可以在「管理 / 系統設定」中修改此預設行為。
failureCount: 失敗 {n}
lastFailed: '{t}呼叫失敗'
lastRan: '{t}呼叫'
lastSucceeded: '{t}呼叫成功'
successCount: 成功 {n}
</i18n>
<!-- Generated by OpenCC END -->

<template>
  <div id="aside-script-content" class="aside-inner-content">
    <div class="jump-to-select-wrap">
      <el-select class="jump-to-select"
        :placeholder="$t('Jump to...')"
        :no-data-text="$t('No Data')"
        size="small"
        clearable
        filterable
        :filter-method="T.debounce(doFilter)"
        v-model="selectFilterText">
        <el-option
          v-for="item in selectShowOptions"
          :key="item.id"
          :label="item.label"
          :value="item.id">
          <span class="select-item-name">
            <i v-if="item.type === 'scriptSet'" class="fa fa-fw fa-folder"></i>
            <i v-else-if="item.type === 'script'" class="fa fa-fw fa-file-code-o"></i>
            <el-tag v-else-if="item.type === 'func'" type="info" size="mini"><strong class="code-font">def</strong></el-tag>
            {{ item.label }}
          </span>
          <code class="select-item-id code-font">ID: {{ item.id }}</code>
        </el-option>
      </el-select>
    </div>

    <el-tree class="aside-tree"
      v-loading="loading"
      :data="data"
      :highlight-current="true"
      :default-expand-all="false"
      :default-expanded-keys="defaultExpandedNodeKeys"
      :auto-expand-parent="false"
      :expand-on-click-node="false"
      :indent="10"
      node-key="id"
      v-on:node-expand="onExpandNode"
      v-on:node-collapse="onCollapseNode"
      ref="tree">

      <span
        slot-scope="{node, data}"
        class="aside-tree-node"
        :entry-id="data.id"
        @click="openEntity(data)">

        <!-- Menu -->
        <el-popover
          placement="right-start"
          trigger="hover"
          popper-class="aside-tip"
          :disabled="!data.id && !data.tip"
          v-model="data.showPopover">

          <div slot="reference" class="aside-item">
            <!-- Items -->
            <span>
              <el-link v-if="data.type === 'refresh'" type="primary">
                <i class="fa fa-fw fa-refresh"></i> {{ $t('Refresh') }}
              </el-link>
              <el-link v-else-if="data.type === 'addScriptSet'" type="primary">
                <i class="fa fa-fw fa-plus"></i> {{ $t('New Script Set') }}
              </el-link>
              <el-link v-else-if="data.type === 'scriptSetHiddenTip'" type="info">
                <i class="fa fa-fw fa-eye-slash"></i> <i>{{ $t('Some Script Sets have been hidden') }}</i>
              </el-link>
              <div v-else :class="{'text-watch': [ 'builtin', 'scriptMarket' ].indexOf(data.origin) >= 0}">
                <template v-if="data.type === 'scriptSet'">
                  <i v-if="data.origin === 'builtin'" class="fa fa-fw fa-microchip"></i>
                  <i v-else-if="data.origin === 'scriptMarket'" class="fa fa-fw fa-shopping-cart"></i>
                  <i v-else-if="data.origin === 'blueprint'" class="fa fa-fw fa-sitemap fa-rotate-270"></i>
                  <i v-else class="fa fa-fw" :class="[node.expanded ? 'fa-folder-open':'fa-folder']"></i>
                </template>
                <i v-else-if="data.type === 'script'" class="fa fa-fw fa-file-code-o"></i>
                <el-tag v-else-if="data.type === 'func'" type="info" size="mini"><strong class="code-font">def</strong></el-tag>

                <el-tag v-if="data.isCodeEdited" type="danger" size="mini">{{ $t('Edited') }}</el-tag>
                <span :class="{'text-strong': data.isPinned}">{{ node.label }}</span>
              </div>
            </span>

            <!-- Status icon -->
            <div>
              <el-tooltip v-if="data.type === 'func' && T.notNothing(data.queue)"
                effect="dark" :content="$t('Assigned to queue #{queue}', { queue: data.queue})" placement="top" :enterable="false">
                <el-tag size="mini">#{{ data.queue }}</el-tag>
              </el-tooltip>
              <el-tooltip v-if="data.hasLockConfig" effect="dark" :content="$t('Others are allowed to operate')" placement="top" :enterable="false">
                <i class="fa fa-fw fa-users" :class="(data.isLockedByOther ? 'text-bad' : 'text-good') + (data.isLockedByScriptSet ? '-light text-small' : '')"></i>
              </el-tooltip>
              <el-tooltip v-if="data.isLocked" effect="dark" :content="data.isLockedByOther ? $t('Locked by other user ({user})', { user: data.lockedByUser }) : $t('Locked by you')" placement="top" :enterable="false">
                <i class="fa fa-fw fa-lock" :class="(data.isLockedByOther ? 'text-bad' : 'text-good') + (data.isLockedByScriptSet ? '-light text-small' : '')"></i>
              </el-tooltip>
              <el-tooltip v-if="data.isPinned"
                effect="dark" :content="$t('Pinned')" placement="top" :enterable="false">
                <i class="fa fa-fw fa-thumb-tack text-bad"></i>
              </el-tooltip>
            </div>
          </div>

          <!-- Tips -->
          <div v-if="data.tip" class="aside-tree-node-tip">
            <i class="fa fa-fw fa-info-circle"></i> <strong v-if="data.tipTitle">{{ data.tipTitle }}<br></strong>
            <span v-html="data.tip" />
          </div>

          <!-- Basic info -->
          <div v-if="data.id" class="aside-tree-node-description">
            <InfoBlock type="warning" v-if="data.origin === 'builtin'" :title="$t('Built-in Script Set')" />
            <InfoBlock type="warning" v-if="data.origin === 'scriptMarket'" :title="$t('Installed form Script Market')" />

            <div>
              <span class="text-info">ID</span>
              &nbsp;<code class="text-main">{{ data.id }}</code>
              <CopyButton :content="data.id" />
            </div>

            <pre v-if="(data.type === 'scriptSet' || data.type === 'script') && data.description">{{ data.description }}</pre>
          </div>

          <!-- Sample code -->
          <template v-if="data.sampleCodeImportFromSameScriptSet">
            <div class="aside-tree-node-sample">
              <span class="text-info">{{ $t('Example (In Same Script Set)') }}</span>
              <CopyButton :content="data.sampleCodeImportFromSameScriptSet" />

              <pre>{{ data.sampleCodeImportFromSameScriptSet }}</pre>
            </div>
          </template>
          <template v-if="data.sampleCodeImportFromOtherScriptSet">
            <div class="aside-tree-node-sample">
              <span class="text-info">{{ $t('Example (In Other Script Set)') }}</span>
              <CopyButton :content="data.sampleCodeImportFromOtherScriptSet" />

              <pre>{{ data.sampleCodeImportFromOtherScriptSet }}</pre>
            </div>
          </template>

          <!-- Edited tips -->
          <template v-if="data.isCodeEdited">
            <br>
            <div class="code-edited-tip">
              <span class="text-bad">{{ $t('Code edited but not published yet') }}<br>{{ $t('Import/Calling will run the published version') }}</span>
            </div>
          </template>

          <!-- Operation -->
          <template v-if="[ 'scriptSet', 'script', 'func' ].indexOf(data.type) >= 0">
            <template v-if="[ 'scriptSet', 'script' ].indexOf(data.type) >= 0">
              <br>
              <div class="controls-bar">
                <el-button-group>
                  <!-- Quick view (Script) -->
                  <el-button v-if="data.type === 'script'"
                    size="small"
                    :disabled="!can(data, [ 'scriptSet_readScriptCode', 'scriptSet_editScriptCode', 'script_readCode', 'script_editCode' ])"
                    @click="showQuickViewWindow(data)">
                    <i class="fa fa-fw fa-window-restore"></i>
                    {{ $t('Quick View') }}
                  </el-button>

                  <!-- Add Script -->
                  <el-button
                    size="small"
                    :disabled="!can(data, [ 'scriptSet_editScriptCode' ])"
                    @click="openEntity(data, 'add')">
                    <i class="fa fa-fw fa-plus"></i>
                    {{ $t('New Script') }}
                  </el-button>

                  <!-- Setup -->
                  <el-button
                    size="small"
                    :disabled="!can(data, data.type === 'scriptSet' ? [ 'scriptSet_setup' ] : [ 'scriptSet_setupScript', 'script_setup'])"
                    @click="openEntity(data, 'setup')">
                    <i class="fa fa-fw fa-wrench"></i>
                    {{ $t('Setup') }}
                  </el-button>

                  <!-- Lock / Unlock -->
                  <el-button
                    size="small"
                    :class="data.isLocked ? 'button-group-actived' : ''"
                    :disabled="!can(data)"
                    @click="openEntity(data, 'lockConfig')">
                    <i class="fa fa-fw fa-lock"></i>
                    {{ $t('Lock') }}
                    &nbsp;|&nbsp;
                    <i class="fa fa-fw fa-users"></i>
                    {{ $t('Members') }}
                  </el-button>

                  <!-- Pin (Script Set) -->
                  <el-button v-if="data.type === 'scriptSet'"
                    size="small"
                    :class="data.isPinned ? 'button-group-actived' : ''"
                    :disabled="!can(data, [ 'scriptSet_setup' ])"
                    v-prevent-re-click @click="pinData(data.type, data.id, !data.isPinned)">
                    <i class="fa fa-fw" :class="[data.isPinned ? 'fa-thumb-tack fa-rotate-270' : 'fa-thumb-tack']"></i>
                    {{ data.isPinned ? $t('Unpin') : $t('Pin') }}
                  </el-button>
                </el-button-group>

                <el-button-group v-if="data.type === 'scriptSet'">
                  <!-- Clone -->
                  <el-button
                    size="small"
                    :disabled="!can(data, [ 'scriptSet_readScriptCode', 'scriptSet_editScriptCode' ])"
                    @click="cloneData(data)">
                    <i class="fa fa-fw fa-files-o"></i>
                    {{ $t('Clone') }}
                  </el-button>

                  <!-- Export -->
                  <el-button
                    size="small"
                    :disabled="!can(data, [ 'scriptSet_readScriptCode', 'scriptSet_editScriptCode' ])"
                    @click="exportData(data)">
                    <i class="fa fa-fw fa-cloud-download"></i>
                    {{ $t('Export') }}
                  </el-button>
                </el-button-group>
              </div>
            </template>

            <!-- Related info -->
            <div class="goto-links">
              <!-- Go to Script Publish History -->
              <el-link v-if="data.type === 'script' && $store.getters.showExperimentalFeature('ScriptPublishHistory')"
                @click="common.goToPage('script-publish-history-list', { scriptId: data.id })">
                <i class="fa fa-fw fa-history"></i>
                {{ $t('Go to Script Publish History') }}
              </el-link>

              <!-- Show Sync API, Async API, Cron Job -->
              <el-link v-if="data.type === 'func'"
                @click="openRelEntity(node, data, 'syncAPI')">
                <i class="fa fa-fw fa-window-restore"></i>
                {{ $t('Sync, Async APIs and Cron Jobs') }}
              </el-link>

              <!-- Go to Task Record -->
              <el-link v-if="data.type === 'func'"
                @click="common.goToPage('task-record-func-list', { funcId: data.id })">
                <i class="fa fa-fw fa-share-square"></i>
                {{ $t('Go to Recent Task Record') }}
              </el-link>

              <!-- Go to Script Market -->
              <el-tooltip effect="dark" :disabled="!!data.scriptMarketId" :content="$t('The Script Market has been removed')" placement="right">
                <el-link v-if="data.origin === 'scriptMarket' && data.originId"
                  :disabled="!data.scriptMarketId"
                  @click="openEntity(data, 'scriptMarket')">
                  <i class="fa fa-fw fa-share-square"></i>
                  {{ $t('Go to Script Market') }}
                </el-link>
              </el-tooltip>
            </div>
          </template>
        </el-popover>
      </span>
    </el-tree>

    <QuickViewWindow ref="quickViewWindow"></QuickViewWindow>

    <APIExampleDialog ref="apiExampleDialog"></APIExampleDialog>

    <ScriptSetSetup ref="ScriptSetSetup" />
    <ScriptSetup ref="ScriptSetup" />

    <ScriptSetLockConfig ref="ScriptSetLockConfig" />
    <ScriptLockConfig ref="ScriptLockConfig" />

    <el-dialog
      :visible.sync="showRelEntity"
      width="1050px">
      <div slot="title">
        <FuncInfo
          :id="relEntityFunc.id"
          :title="relEntityFunc.title"
          :definition="relEntityFunc.definition"
          :hideGotoFunc="true" />
        <span class="press-esc-to-close-tip">{{ $t('Press ESC to close') }}</span>
      </div>
      <el-tabs v-model="relEntityTarget">
        <el-tab-pane name="syncAPI">
          <span slot="label"><i class="fa fa-fw fa-link"></i> {{ $t('Sync API') }}</span>

          <transition name="fade">
          <el-table
            class="common-table" v-if="showRelEntityData"
            :data="relEntities.syncAPIs">
            <el-table-column :label="$t('Sync API')">
              <template slot-scope="scope">
                <span class="text-info">ID</span>
                &nbsp;<code class="text-main">{{ scope.row.id }}</code>
                <CopyButton :content="scope.row.id" />

                <template v-if="scope.row.note">
                  <br>
                  <span class="text-info">&#12288;{{ $t('Note') }}{{ $t(':') }}</span>
                  <span>{{ scope.row.note }}</span>
                </template>
              </template>
            </el-table-column>

            <el-table-column :label="$t('Config')" width="220">
              <template slot-scope="scope">
                <span class="text-info">{{ $t('Auth') }}{{ $t(':') }}</span>
                <el-tooltip :content="scope.row.apia_title" :disabled="!!!scope.row.apia_title" placement="right">
                  <span :class="{ 'text-main': !!scope.row.apia_id }">{{ C.API_AUTH_MAP.get(scope.row.apia_type).name }}</span>
                </el-tooltip>

                <br>
                <span class="text-info">{{ $t('Expires') }}{{ $t(':') }}</span>
                <span v-if="!scope.row.expireTime">-</span>
                <template v-else>
                  <RelativeDateTime :datetime="scope.row.expireTime"
                    :class="T.isExpired(scope.row.expireTime) ? 'text-bad' : 'text-good'" />
                </template>

                <br>
                <span class="text-info">{{ $t('Throttling') }}{{ $t(':') }}</span>
                <span v-if="T.isNothing(scope.row.throttlingJSON)">-</span>
                <el-tooltip v-else placement="right">
                  <div slot="content">
                    <template v-for="opt in C.SYNC_API_THROTTLING">
                      <span v-if="scope.row.throttlingJSON[opt.key]">{{ $tc(opt.name, scope.row.throttlingJSON[opt.key]) }}<br></span>
                    </template>
                  </div>
                  <span class="text-bad">{{ $t('ON') }}</span>
                </el-tooltip>
              </template>
            </el-table-column>

            <el-table-column :label="$t('Status')" width="220">
              <template slot-scope="scope">
                <span v-if="scope.row.isDisabled" class="text-bad"><i class="fa fa-fw fa-ban"></i> {{ $t('Disabled') }}</span>
                <span v-else class="text-good"><i class="fa fa-fw fa-check"></i> {{ $t('Enabled') }}</span>

                <template v-if="scope.row.lastStartTime">
                  <br>
                  <span v-if="scope.row.lastStatus === 'success'" class="text-good">
                    <i class="fa fa-fw fa-check"></i> {{ $t('lastSucceeded', { t: T.toNow(scope.row.lastStartTime) }) }}
                  </span>
                  <span v-else-if="scope.row.lastStatus === 'failure'" class="text-bad">
                    <i class="fa fa-fw fa-times"></i> {{ $t('lastFailed', { t: T.toNow(scope.row.lastStartTime) }) }}
                  </span>
                  <span v-else class="text-main">
                    <i class="fa fa-fw fa-clock-o"></i> {{ $t('lastRan', { t: T.toNow(scope.row.lastStartTime) }) }}
                  </span>

                  <br>
                  <i class="fa fa-fw fa-pie-chart text-info"></i>
                  <span :class="{ 'text-good': !!scope.row.recentSuccessCount }">{{ $t('successCount', { n: T.numberLimit(scope.row.recentSuccessCount) }) }}</span>
                  / <span :class="{ 'text-bad': !!scope.row.recentFailureCount }">{{ $t('failureCount', { n: T.numberLimit(scope.row.recentFailureCount) }) }}</span>
                </template>
              </template>
            </el-table-column>

            <el-table-column align="right" width="180">
              <template slot="header" slot-scope="scope">
                <el-link type="primary" @click="$router.push({name: 'sync-api-list'})">
                  {{ $t('Sync API List') }}
                  <i class="fa fa-fw fa-share-square"></i>
                </el-link>
              </template>
              <template slot-scope="scope">
                <el-link :disabled="T.isNothing(scope.row.func_id)" @click="showAPI(scope.row, '/api/v1/sync/:id')">{{ $t('Example') }}</el-link>
              </template>
            </el-table-column>
          </el-table>
          </transition>
        </el-tab-pane>

        <el-tab-pane name="asyncAPI">
          <span slot="label"><i class="fa fa-fw fa-tasks"></i> {{ $t('Async API') }}</span>

          <transition name="fade">
          <el-table
            class="common-table" v-if="showRelEntityData"
            :data="relEntities.asyncAPIs">
            <el-table-column :label="$t('Async API')">
              <template slot-scope="scope">
                <span class="text-info">ID</span>
                &nbsp;<code class="text-main">{{ scope.row.id }}</code>
                <CopyButton :content="scope.row.id" />

                <template v-if="scope.row.note">
                  <br>
                  <span class="text-info">&#12288;{{ $t('Note') }}{{ $t(':') }}</span>
                  <span>{{ scope.row.note }}</span>
                </template>
              </template>
            </el-table-column>

            <el-table-column :label="$t('Config')" width="220">
              <template slot-scope="scope">
                <span class="text-info">{{ $t('Auth') }}{{ $t(':') }}</span>
                <el-tooltip :content="scope.row.apia_title" :disabled="!!!scope.row.apia_title" placement="right">
                  <span :class="{ 'text-main': !!scope.row.apia_id }">{{ C.API_AUTH_MAP.get(scope.row.apia_type).name }}</span>
                </el-tooltip>
              </template>
            </el-table-column>

            <el-table-column :label="$t('Status')" width="220">
              <template slot-scope="scope">
                <span v-if="scope.row.isDisabled" class="text-bad"><i class="fa fa-fw fa-ban"></i> {{ $t('Disabled') }}</span>
                <span v-else class="text-good"><i class="fa fa-fw fa-check"></i> {{ $t('Enabled') }}</span>

                <template v-if="scope.row.lastStartTime">
                  <br>
                  <span v-if="scope.row.lastStatus === 'success'" class="text-good">
                    <i class="fa fa-fw fa-check"></i> {{ $t('lastSucceeded', { t: T.toNow(scope.row.lastStartTime) }) }}
                  </span>
                  <span v-else-if="scope.row.lastStatus === 'failure'" class="text-bad">
                    <i class="fa fa-fw fa-times"></i> {{ $t('lastFailed', { t: T.toNow(scope.row.lastStartTime) }) }}
                  </span>
                  <span v-else class="text-main">
                    <i class="fa fa-fw fa-clock-o"></i> {{ $t('lastRan', { t: T.toNow(scope.row.lastStartTime) }) }}
                  </span>

                  <br>
                  <i class="fa fa-fw fa-pie-chart text-info"></i>
                  <span :class="{ 'text-good': !!scope.row.recentSuccessCount }">{{ $t('successCount', { n: T.numberLimit(scope.row.recentSuccessCount) }) }}</span>
                  / <span :class="{ 'text-bad': !!scope.row.recentFailureCount }">{{ $t('failureCount', { n: T.numberLimit(scope.row.recentFailureCount) }) }}</span>
                </template>
              </template>
            </el-table-column>

            <el-table-column align="right" width="180">
              <template slot="header" slot-scope="scope">
                <el-link type="primary" @click="$router.push({name: 'async-api-list'})">
                  {{ $t('Async API List') }}
                  <i class="fa fa-fw fa-share-square"></i>
                </el-link>
              </template>
              <template slot-scope="scope">
                <el-link :disabled="T.isNothing(scope.row.func_id)" @click="showAPI(scope.row, '/api/v1/async/:id')">{{ $t('Example') }}</el-link>
              </template>
            </el-table-column>
          </el-table>
          </transition>
        </el-tab-pane>

        <el-tab-pane name="cronJob">
          <span slot="label"><i class="fa fa-fw fa-clock-o"></i> {{ $t('Cron Job') }}</span>

          <transition name="fade">
          <el-table
            class="common-table" v-if="showRelEntityData"
            :data="relEntities.cronJobs">
            <el-table-column :label="$t('Cron Job')">
              <template slot-scope="scope">
                <span class="text-info">ID</span>
                &nbsp;<code class="text-main">{{ scope.row.id }}</code>
                <CopyButton :content="scope.row.id" />

                <template v-if="scope.row.note">
                  <br>
                  <span class="text-info">&#12288;{{ $t('Note') }}{{ $t(':') }}</span>
                  <span>{{ scope.row.note }}</span>
                </template>
              </template>
            </el-table-column>

            <el-table-column :label="$t('Config')" width="220">
              <template slot-scope="scope">
                <span class="text-info">{{ $t('Cron Expr') }}{{ $t(':') }}</span>
                <template v-if="scope.row.func_extraConfigJSON && scope.row.func_extraConfigJSON.fixedCronExpr">
                  <code class="text-main">{{ scope.row.func_extraConfigJSON.fixedCronExpr }}</code>
                  <el-tag size="mini">{{ $t('Fixed') }}</el-tag>
                </template>
                <code v-else-if="scope.row.cronExpr" class="text-main">{{ scope.row.cronExpr }}</code>
                <span v-else class="text-bad">{{ $t('Not Set') }}</span>

                <br>
                <span class="text-info">{{ $t('Created') }}{{ $t(':') }}</span>
                <RelativeDateTime :datetime="scope.row.createTime" from-now-limit="before" />

                <br>
                <span class="text-info">{{ $t('Expires') }}{{ $t(':') }}</span>
                <span v-if="!scope.row.expireTime">-</span>
                <template v-else>
                  <RelativeDateTime :datetime="scope.row.expireTime"
                    :class="T.isExpired(scope.row.expireTime) ? 'text-bad' : 'text-good'" />
                </template>
              </template>
            </el-table-column>

            <el-table-column :label="$t('Status')" width="220">
              <template slot-scope="scope">
                <span v-if="scope.row.isDisabled" class="text-bad"><i class="fa fa-fw fa-ban"></i> {{ $t('Disabled') }}</span>
                <span v-else class="text-good"><i class="fa fa-fw fa-check"></i> {{ $t('Enabled') }}</span>

                <template v-if="scope.row.lastStartTime">
                  <br>
                  <span v-if="scope.row.lastStatus === 'success'" class="text-good">
                    <i class="fa fa-fw fa-check"></i> {{ $t('lastSucceeded', { t: T.toNow(scope.row.lastStartTime) }) }}
                  </span>
                  <span v-else-if="scope.row.lastStatus === 'failure'" class="text-bad">
                    <i class="fa fa-fw fa-times"></i> {{ $t('lastFailed', { t: T.toNow(scope.row.lastStartTime) }) }}
                  </span>
                  <span v-else class="text-main">
                    <i class="fa fa-fw fa-clock-o"></i> {{ $t('lastRan', { t: T.toNow(scope.row.lastStartTime) }) }}
                  </span>

                  <br>
                  <i class="fa fa-fw fa-pie-chart text-info"></i>
                  <span :class="{ 'text-good': !!scope.row.recentSuccessCount }">{{ $t('successCount', { n: T.numberLimit(scope.row.recentSuccessCount) }) }}</span>
                  / <span :class="{ 'text-bad': !!scope.row.recentFailureCount }">{{ $t('failureCount', { n: T.numberLimit(scope.row.recentFailureCount) }) }}</span>
                </template>
              </template>
            </el-table-column>

            <el-table-column align="right" width="180">
              <template slot="header" slot-scope="scope">
                <el-link type="primary" @click="$router.push({name: 'cron-job-list'})">
                  {{ $t('Cron Job List') }}
                  <i class="fa fa-fw fa-share-square"></i>
                </el-link>
              </template>
              <template slot-scope="scope">
                <el-link @click="startCronJobManually(scope.row)" :disabled="!scope.row.func_id">
                  {{ $t('Run') }}
                </el-link>
              </template>
            </el-table-column>
          </el-table>
          </transition>
        </el-tab-pane>
      </el-tabs>
    </el-dialog>
  </div>
</template>

<script>
import FileSaver from 'file-saver';

import QuickViewWindow from '@/components/Development/QuickViewWindow'
import APIExampleDialog from '@/components/APIExampleDialog'

import ScriptSetSetup from '@/components/Development/ScriptSetSetup'
import ScriptSetup from '@/components/Development/ScriptSetup'

import ScriptSetLockConfig from '@/components/Development/ScriptSetLockConfig'
import ScriptLockConfig from '@/components/Development/ScriptLockConfig'

export default {
  name: 'AsideScript',
  components: {
    QuickViewWindow,
    APIExampleDialog,

    ScriptSetSetup,
    ScriptSetup,

    ScriptSetLockConfig,
    ScriptLockConfig,
  },
  watch: {
    selectFilterText(val) {
      if (!val) return;
      if (!this.$refs.tree) return;

      let node = this.$refs.tree.getNode(val);
      this.openEntity(node.data, null, { scroll: true });
    },
    '$store.state.scriptListSyncTime': function() {
      this.loadData();
    },
    expandedNodeMap(val) {
      // Record expanded node
      this.$store.commit('updateAsideScript_expandedNodeMap', val);
    },
    relEntityTarget(val) {
      this.showRelEntityData = false;
      this.$nextTick(() => {
        this.showRelEntityData = true;
      });
    },
  },
  methods: {
    doFilter(q) {
      q = (q || '').toLowerCase().trim();
      if (!q) {
        this.selectShowOptions = this.selectOptions;
      } else {
        this.selectShowOptions = this.common.filterByKeywords(q, this.selectOptions);
      }
    },

    // Expand / collapse node by setting `expandedNodeMap`
    onExpandNode(data, node) {
      this.$set(this.expandedNodeMap, data.id, true);
    },
    onCollapseNode(data, node) {
      this.$delete(this.expandedNodeMap, data.id);
    },

    // Select node
    selectNode(nodeKey, options) {
      options = options || {};

      if (!this.$refs.tree) return;
      if (!nodeKey) return;

      setImmediate(() => {
        let node = this.$refs.tree.getNode(nodeKey);
        if (!node || !node.data) return;

        // Select
        this.$refs.tree.setCurrentKey(node.data.id);

        // Expand all parent layer
        let parentNode = node.parent;
        while (parentNode) {
          parentNode.expand();
          this.onExpandNode(parentNode.data, parentNode);

          parentNode = parentNode.parent;
        }

        // Expand current node
        if (options.expandChildren) {
          node.expand();
          this.onExpandNode(node.data, node);
        }

        // Scroll to element
        if (options.scroll) {
          this.common.scrollAside('aside-script-content', node.data.id);
        }
      });
    },
    async loadData() {
      if (this.T.isNothing(this.data)) {
        this.loading = true;
      }

      // Node entity for checking Biz Entity exists
      //    { "<ID>": "scriptSet|script|func" }
      let nodeEntity = {};

      // Script Set, Script, Func map
      //    { "<ID>": {JSON Data} }
      let scriptSetMap = {};
      let scriptMap    = {};
      let funcMap      = {};

      /***** Script Set *****/
      let hasHiddenScriptSets = false;
      let apiRes = await this.T.callAPI_getAll('/api/v1/script-sets/do/list', {
        query: {
          fields: [
            'id',
            'title',
            'description',
            'isLocked',
            'hasLockConfig',
            'lockedByUserId',
            'lockedByUserUsername',
            'lockedByUserName',
            'origin',
            'originId',
            'isPinned',
            'pinTime',
            'smkt_id',
            'lockConfigMemberAllowMap',
          ]
        },
      });
      if (!apiRes || !apiRes.ok) return;

      apiRes.data.forEach(d => {
        // Skip hided Script Sets
        if (this.common.shouldScriptSetHidden(d)) {
          hasHiddenScriptSets = true;
          return;
        }

        // Record node entity
        nodeEntity[d.id] = 'scriptSet';

        // Reduce the number of description lines
        d.description = this.T.limitLines(d.description, 10);

        // Create node data
        let lockedByUser    = d.lockedByUserName || d.lockedByUsername || this.$t('UNKNOWN');
        let isLockedByMe    = d.lockedByUserId === this.$store.getters.userId;
        let isLockedByOther = d.lockedByUserId && !isLockedByMe;
        scriptSetMap[d.id] = {
          id   : d.id,
          label: d.title || d.id,
          type : 'scriptSet',

          lockedByUserId          : d.lockedByUserId,
          lockedByUser            : lockedByUser,
          isLockedByMe            : isLockedByMe,
          isLockedByOther         : isLockedByOther,
          isLocked                : d.isLocked,
          hasLockConfig           : d.hasLockConfig,
          lockConfigMemberAllowMap: d.lockConfigMemberAllowMap,

          isPinned      : d.isPinned,
          pinTime       : d.pinTime,
          origin        : d.origin,
          originId      : d.originId,
          scriptMarketId: d.smkt_id,

          title      : d.title,
          description: d.description,

          children   : [],
          showPopover: false,
        };
        this.common.appendSearchFields(scriptSetMap[d.id], ['id', 'title'])
      });

      /***** Script *****/
      apiRes = await this.T.callAPI_getAll('/api/v1/scripts/do/list', {
        query: {
          fields: [
            'id',
            'title',
            'description',
            'scriptSetId',
            'codeMD5',
            'codeDraftMD5',
            'isLocked',
            'isLockedByScriptSet',
            'hasLockConfig',
            'lockedByUserId',
            'lockedByUserUsername',
            'lockedByUserName',
            'lockConfigMemberAllowMap',
          ]
        },
      });
      if (!apiRes || !apiRes.ok) return;

      window._DFF_scriptIds = [];
      apiRes.data.forEach(d => {
        // Skip if Script Set not exists
        if (!scriptSetMap[d.scriptSetId]) return;

        window._DFF_scriptIds.push(d.id);

        // Record node entity
        nodeEntity[d.id] = 'script';

        // Reduce the number of description lines
        d.description = this.T.limitLines(d.description, 10);

        // Sample code
        let shortScriptId = d.id.split('__').slice(1).join('__');
        let sampleCodeImportFromSameScriptSet  = `import __${shortScriptId} as ${shortScriptId}`;
        let sampleCodeImportFromOtherScriptSet = `import ${d.id} as ${shortScriptId}`;

        // Create node data
        let isCodeEdited    = d.codeMD5 !== d.codeDraftMD5;
        let lockedByUser    = d.lockedByUserName || d.lockedByUsername || this.$t('UNKNOWN');
        let isLockedByMe    = d.lockedByUserId === this.$store.getters.userId;
        let isLockedByOther = d.lockedByUserId && !isLockedByMe;
        scriptMap[d.id] = {
          id          : d.id,
          scriptSetId : d.scriptSetId,
          label       : d.title || shortScriptId,
          type        : 'script',
          isCodeEdited: isCodeEdited,

          lockedByUserId          : d.lockedByUserId,
          lockedByUser            : lockedByUser,
          isLockedByMe            : isLockedByMe,
          isLockedByOther         : isLockedByOther,
          isLocked                : d.isLocked,
          isLockedByScriptSet     : d.isLockedByScriptSet,
          hasLockConfig           : d.hasLockConfig,
          lockConfigMemberAllowMap: d.lockConfigMemberAllowMap,

          title      : d.title,
          description: d.description,

          sampleCodeImportFromSameScriptSet : sampleCodeImportFromSameScriptSet,
          sampleCodeImportFromOtherScriptSet: sampleCodeImportFromOtherScriptSet,

          children   : [],
          showPopover: false,
        };
        this.common.appendSearchFields(scriptMap[d.id], ['id', 'title'])

        // Add to parent layer's "children" field
        if (scriptSetMap[d.scriptSetId]) {
          scriptSetMap[d.scriptSetId].children.push(scriptMap[d.id]);
        }
      });

      /***** Func *****/
      apiRes = await this.T.callAPI_getAll('/api/v1/funcs/do/list', {
        query: {
          fields: [
            'id',
            'title',
            'description',
            'definition',
            'scriptSetId',
            'scriptId',
            'sset_type',
            'integration',
            'extraConfigJSON',
          ]
        },
      });
      if (!apiRes || !apiRes.ok) return;

      window._DFF_funcIds = [];
      apiRes.data.forEach(d => {
        // Skip if Script not exists
        if (!scriptMap[d.scriptId]) return;

        window._DFF_funcIds.push(d.id);

        // Get parent Script
        let parentScript = scriptMap[d.scriptId] || {};

        // Record node entity
        nodeEntity[d.id] = 'func';

        // Reduce the number of description lines
        d.description = this.T.limitLines(d.description, 10);

        // Sample code
        let shortScriptId = d.scriptId.split('__').slice(1).join('__');
        let sampleCodeImportFromSameScriptSet  = `import __${shortScriptId} as ${shortScriptId}\n${shortScriptId}.${d.definition}`;
        let sampleCodeImportFromOtherScriptSet = `import ${d.scriptId} as ${shortScriptId}\n${shortScriptId}.${d.definition}`;

        // Create node data
        funcMap[d.id] = {
          id         : d.id,
          scriptSetId: d.scriptSetId,
          scriptId   : d.scriptId,
          label      : d.title || d.id,
          type       : 'func',
          definition : d.definition,

          lockedByUserId          : parentScript.lockedByUserId           || null,
          lockConfigMemberAllowMap: parentScript.lockConfigMemberAllowMap || {},

          title      : d.title,
          description: d.description,
          queue      : (d.extraConfigJSON || {}).queue,

          disabled: !this.$store.state.codeEditor_isCodeLoaded,

          sampleCodeImportFromSameScriptSet : sampleCodeImportFromSameScriptSet,
          sampleCodeImportFromOtherScriptSet: sampleCodeImportFromOtherScriptSet,

          showPopover: false,
        };
        this.common.appendSearchFields(funcMap[d.id], ['id', 'title'])

        // Add to parent layer's "children" field
        if (scriptMap[d.scriptId]) {
          scriptMap[d.scriptId].children.push(funcMap[d.id]);
        }
      });

      // Convert to tree data.
      // Add [Refresh], [Add Script Set], [Add Script] items
      let treeData = Object.values(scriptSetMap);
      treeData.forEach(d => {
        d.children.sort(this.common.asideItemSorter);
      })
      treeData.sort(this.common.scriptSetSorter);
      treeData.unshift({ type: 'addScriptSet' });
      treeData.unshift({ type: 'refresh' });

      // Add notice item if has hidden Scrips
      if (hasHiddenScriptSets) {
        let scriptHiddenTip = this.$t("In most cases, the Script Sets that users doesn't need to care about are hidden by default.");
        if (this.$store.getters.isAdmin) {
          scriptHiddenTip += '<br>' + this.$t("You can change the default behavior in [Management / System Settings].");
        }

        treeData.push({
          type    : 'scriptSetHiddenTip',
          tipTitle: this.$t('Some Script Sets have been hidden'),
          tip     : scriptHiddenTip,
        });
      }

      // Clear invalid data for expanded node map (node not exists)
      let _d = this.T.jsonCopy(this.$store.state.asideScript_expandedNodeMap);
      for (let nodeId in _d) {
        if (!nodeEntity[nodeId]) delete _d[nodeId];
      }
      this.$store.commit('updateAsideScript_expandedNodeMap', _d);

      // Clear invalid data for Code Editor highlighted line config (Script not exists, no highlighted line)
      _d = this.T.jsonCopy(this.$store.state.codeEditor_highlightedLineConfigMap);
      for (let scriptId in _d) {
        if (!scriptMap[scriptId] || this.T.isNothing(_d[scriptId])) delete _d[scriptId];
      }
      this.$store.commit('updateCodeEditor_highlightedLineConfigMap', _d);

      // Clear invalid data for Code Viewer highlighted line config (Script not exists, no highlighted line)
      _d = this.T.jsonCopy(this.$store.state.codeViewer_highlightedLineConfigMap);
      for (let scriptId in _d) {
        if (!scriptMap[scriptId] || this.T.isNothing(_d[scriptId])) delete _d[scriptId];
      }
      this.$store.commit('updateCodeViewer_highlightedLineConfigMap', _d);

      // Generate selector options
      let selectOptions = [];
      [scriptSetMap, scriptMap, funcMap].forEach(x => {
        let _options = Object.values(x);
        _options.sort(this.common.asideItemSorter);
        selectOptions = selectOptions.concat(_options)
      });

      // Load data
      this.loading           = false;
      this.data              = treeData;
      this.selectOptions     = selectOptions;
      this.selectShowOptions = selectOptions;

      this.scriptSetMap = scriptSetMap;
      this.scriptMap    = scriptMap;
      this.funcMap      = funcMap;

      // Expand node automatically
      this.expandedNodeMap = this.$store.state.asideScript_expandedNodeMap || {};
      this.defaultExpandedNodeKeys = Object.keys(this.expandedNodeMap);

      // Select automatically
      this.selectNode(this.$route.params.id, { scroll: true });
    },
    async pinData(dataType, dataId, isPinned) {
      let apiPath   = null;
      let okMessage = null;
      switch(dataType) {
        case 'scriptSet':
          apiPath   = '/api/v1/script-sets/:id/do/modify';
          okMessage = isPinned
                    ? this.$t('Script Set pinned')
                    : this.$t('Script Set unpinned');
          break;

        default:
          return;
      }
      let apiRes = await this.T.callAPI('post', apiPath, {
        params  : { id: dataId },
        body    : { data: { isPinned: isPinned } },
        feedback: { okMessage: okMessage },
      });
      if (!apiRes || !apiRes.ok) return;

      this.$store.commit('updateScriptListSyncTime');
    },
    async cloneData(data) {
      data.showPopover = false;

      let promptOpt = {
        inputValidator: v => {
          if (v.length <= 0) {
            return this.$t('Please input ID');
          } else if (v.length > 32) {
            return this.$t('Script Set ID too long');
          } else if (!v.match(/^[a-zA-Z0-9_]*$/g)) {
            return this.$t('Only alphabets, numbers and underscore are allowed');
          } else if (!v.match(/^[^0-9]/g)) {
            return this.$t('Cannot not starts with a number');
          }
          return true;
        }
      }
      let newScriptSetId = await this.T.prompt(this.$t('Please input new Script Set ID'), `${data.id}_2`, promptOpt);
      if (!newScriptSetId) return;

      // Check ID duplication
      let apiRes = await this.T.callAPI_getOne('/api/v1/script-sets/do/list', newScriptSetId);
      if (apiRes.data) {
        return this.T.alert(this.$t('Inputed Script Set ID already exists'));
      }

      // Call API
      apiRes = await this.T.callAPI('post', '/api/v1/script-sets/:id/do/clone', {
        params  : { id: data.id },
        body    : { newId: newScriptSetId },
        feedback: { okMessage: this.$t('Script Set cloned') },
      });
      if (!apiRes || !apiRes.ok) return;

      this.$store.commit('updateScriptListSyncTime');
    },
    async exportData(data) {
      data.showPopover = false;

      let opt = {
        respType: 'blob',
        packResp: true,
        body    : { scriptSetIds: [ data.id ], note: this.$t('Export Script Set') },
      };

      let apiRes = await this.T.callAPI('post', '/api/v1/script-sets/do/export', opt);
      if (!apiRes || !apiRes.ok) return;

      let blob = new Blob([apiRes.data], {type: apiRes.extra.contentType});

      // File name
      let fileNameParts = [
        data.id,
        this.M().utcOffset('+08:00').format('YYYYMMDD_HHmmss'),
      ];
      let fileName = fileNameParts.join('-') + '.zip';
      FileSaver.saveAs(blob, fileName);

      await this.T.alert(this.$t('Script Set exported'), 'success');
    },
    showQuickViewWindow(data) {
      data.showPopover = false;

      this.$refs.quickViewWindow.showWindow(data.id);
    },

    openEntity(data, target, options) {
      options = options || {};

      data.showPopover = false;

      let setCodeLoading = (nextScriptId) => {
        if (this.$route.name === 'code-editor' && this.$route.params.id !== nextScriptId) {
          this.$store.commit('updateCodeEditor_isCodeLoaded', false);
        }
      }

      switch(data.type) {
        // Refresh
        case 'refresh':
          this.loadData();
          break;

        // [Add Script Set]
        case 'addScriptSet':
          this.$refs.ScriptSetSetup.loadData();
          break;

        // Script Set
        case 'scriptSet':
          if (target === 'add') {
            // Show Script Set add panel
            options.expandChildren = true;
            this.selectNode(data.id, options);
            this.$refs.ScriptSetup.loadData(data.id);

          } else if (target === 'setup') {
            // Show Script Set setup panel
            options.expandChildren = false;
            this.selectNode(data.id, options);
            this.$refs.ScriptSetSetup.loadData(data.id);

          } else if (target === 'lockConfig') {
            // show Script Set lock config panel
            options.expandChildren = false;
            this.selectNode(data.id, options);
            this.$refs.ScriptSetLockConfig.loadData(data.id);

          } else if (target === 'scriptMarket') {
            // Go to Script Market
            this.$router.push({
              name  : 'script-market-contents',
              params: {id: data.originId},
            });

          } else {
            // Click Scirpt Set
            options.expandChildren = true;
            this.selectNode(data.id, options);
          }
          break;

        // Script
        case 'script':
          if (target === 'add') {
            // Show Script add panel
            options.expandChildren = true;
            this.selectNode(data.id, options);
            this.$refs.ScriptSetup.loadData(data.scriptSetId);

          } else if (target === 'setup') {
            // Show Script setup panel
            options.expandChildren = false;
            this.selectNode(data.id, options);
            this.$refs.ScriptSetup.loadData(data.id.split('__')[0], data.id);

          } else if (target === 'lockConfig') {
            // Show Script lock config panel
            options.expandChildren = false;
            this.selectNode(data.id, options);
            this.$refs.ScriptLockConfig.loadData(data.id);

          } else {
            // Click Scirpt
            if ((this.$route.name === 'code-viewer' || this.$route.name === 'code-editor')
                && data.id === this.$route.params.id) {
              // Click current Script in Code Viewer / Editor
              options.expandChildren = true;
              this.selectNode(data.id, options);

            } else {
              // Click Script
              options.expandChildren = true;
              this.selectNode(data.id, options);

              setCodeLoading(data.id);

              this.$router.push({
                name  : 'code-viewer',
                params: { id: data.id },
              });
            }
          }
          break;

        // Func
        case 'func':
          // Highlight selected Func
          this.$store.commit('updateEditor_selectedItemId', data.id);

          if ((this.$route.name === 'code-viewer' || this.$route.name === 'code-editor')
                && data.scriptId === this.$route.params.id) {
            // Click Func in current Script in Code Viewer / Editor
            options.expandChildren = false;
            this.selectNode(data.id, options);

          } else {
            // Click Func
            options.expandChildren = false;
            this.selectNode(data.id, options);

            setCodeLoading(data.scriptId);
            this.$router.push({
              name  : 'code-viewer',
              params: { id: data.scriptId },
            });
          }

          break;

        // Other
        default:
          console.error(`Unexcepted data type: ${data.type}`);
          break;
      }
    },
    async deleteEntity(data) {
      let apiPath     = null;
      let confirmText = null;
      let finishText  = null;
      switch(data.type) {
        // Script Set
        case 'scriptSet':
          apiPath     = '/api/v1/script-sets/:id/do/delete';
          confirmText = this.$t('Are you sure you want to delete the Script Set?<br><strong class="text-bad">{label}</strong>', { label: data.label });
          finishText  = this.$t('Script Set deleted');
          break;

        // Script
        case 'script':
          apiPath     = '/api/v1/scripts/:id/do/delete';
          confirmText = this.$t('Are you sure you want to delete the Script?<br><strong class="text-bad">{label}</strong>', { label: data.label });
          finishText  = this.$t('Script deleted');
          break;

        default:
          return;
      }
      if (!await this.T.confirm(confirmText)) return;

      this.common.jumpDeletedEntity(data.id);

      let apiRes = await this.T.callAPI(apiPath, {
        params  : { id: data.id },
        feedback: { okMessage: finishText },
      });
      if (!apiRes || !apiRes.ok) return;

      this.$store.commit('updateScriptListSyncTime');

    },

    async loadRelEntityData(data) {
      let apiRes  = null;
      let listOpt = {
        query: {
          origin       : 'user',
          funcId       : data.id,
          pageSize     : 20,
        }
      };

      // Load Sync API list
      apiRes = await this.T.callAPI_get('/api/v1/sync-apis/do/list', listOpt);
      if (apiRes.ok) {
        this.relEntities.syncAPIs = apiRes.data;
      }

      // Load Async API list
      apiRes = await this.T.callAPI_get('/api/v1/async-apis/do/list', listOpt);
      if (apiRes.ok) {
        this.relEntities.asyncAPIs = apiRes.data;
      }

      // Load Cron Job list
      apiRes = await this.T.callAPI_get('/api/v1/cron-jobs/do/list', listOpt);
      if (apiRes.ok) {
        this.relEntities.cronJobs = apiRes.data;
      }
    },
    async openRelEntity(node, data, target) {
      data.showPopover = false;

      await this.loadRelEntityData(data);

      this.relEntityFunc   = data;
      this.relEntityTarget = target;
      this.showRelEntity   = true;
    },
    async showAPI(d, urlPattern) {
      // Get Func info
      let apiRes = await this.T.callAPI_getOne('/api/v1/funcs/do/list', d.funcId);
      if (!apiRes || !apiRes.ok) return;

      // Gen API request example
      let apiURL = this.T.formatURL(urlPattern, {
        baseURL: true,
        params : { id: d.id },
      });

      let funcCallKwargsJSON = {};
      for (let k in d.funcCallKwargsJSON) if (d.funcCallKwargsJSON.hasOwnProperty(k)) {
        if (this.common.isFuncArgumentPlaceholder(d.funcCallKwargsJSON[k])) {
          funcCallKwargsJSON[k] = d.funcCallKwargsJSON[k];
        }
      }
      let apiBody = { kwargs: funcCallKwargsJSON };
      let funcKwargs = apiRes.data.kwargsJSON;

      this.$refs.apiExampleDialog.update(apiURL, apiBody, funcKwargs);
    },
    async startCronJobManually(d) {
      if (!await this.T.confirm(this.$t('Are you sure you want to start the Cron Job manually?'))) return;

      let apiRes = await this.T.callAPI('post', '/api/v1/cron/:id', {
        params  : { id: d.id },
        feedback: { okMessage: this.$t('Cron Job Started Manually') },
      });
    },

    can(data, operation) {
      return this.common.lockConfigCan(data.lockedByUserId, data.lockConfigMemberAllowMap, operation);
    },
  },
  computed: {
    highlightedFuncId() {
      switch(this.$route.name) {
        case 'code-editor':
        case 'code-viewer':
          return this.$store.state.Editor_selectedItemId;

        default:
          return null;
      }
    },
  },
  props: {
  },
  data() {
    return {
      loading: false,
      data   : [],

      selectFilterText : '',
      selectOptions    : [],
      selectShowOptions: [],

      scriptSetMap: {},
      scriptMap   : {},
      funcMap     : {},

      // Since modify the parameter `default-expanded-keys` of el-tree will cause the animation being skipped,
      // default expanded node keys should be an independent data field
      expandedNodeMap        : {},
      defaultExpandedNodeKeys: [],

      showRelEntity    : false,
      showRelEntityData: false,
      relEntityTarget  : '',
      relEntityFunc    : {},
      relEntities: {
        syncAPIs : [],
        asyncAPIs: [],
        cronJobs : [],
      },
    };
  },
  created() {
    this.loadData();
  },
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.goto-links {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding-top: 10px;
}
.goto-links > a {
  font-size: 14px;
  padding-top: 10px;
  display: inline-block;
}
.jump-to-select {
  position: absolute;
  left: 5px;
  right: 5px;
  z-index: 9;
}
.select-item-name {
  float: left;
}
.select-item-id {
  float: right;
  padding-left: 30px;
  font-size: 12px;
}

.aside-tree {
  padding-top: 35px;
  padding-bottom: 10px;
}
.aside-tree-node {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
  height: 31px;
  max-width: 100%;
  overflow: hidden;
}
.aside-tree-node i.fa {
  font-size: 14px;
}
.aside-tip pre {
  padding: 0 0 0 10px;
  margin: 0;
  font-size: 14px;
}
.aside-tree-node-description {

}
.aside-tree-node-sample {
  padding-top: 10px;
  text-align: left;
}
.aside-tree-node-quick-view {
  margin-left: 5px;
}
.aside-tree-node-tip {
  width: 400px;
  word-break: normal;
  line-height: 2;
}

.aside-tree-node > span {
  display: block;
  width: 100%;
}
.aside-item {
  height: 31px;
  display: flex;
  align-items: center;
  justify-content: space-between;

  .fa-stack {
    line-height: 1.9;
  }
}

.code-edited-tip {
  font-size: 14px;
  padding-top: 5px;
  padding-bottom: 5px;
}
.child-nodes-count {
  font-size: 14px;
}
.aside-lock-fix {
  top: -2px;
}
</style>
